建明與永仁於天台相見,不料國平也趕到。永仁事先已報警,想持槍壓著建明到樓下交予警方。不料,於進電梯時被國平擊斃,原來他也是韓琛安裝於警隊的臥底。國平向建明表明身份,希望之後一起合作。但最終建明選擇於電梯中殺死國平,並營造永仁與國平雙雙死於槍戰的假象。事後,心兒於葉校長遺物中發現永仁臥底檔案,恢復其警察身份,並由建明代表行禮。

本幕需要特別注意global current_user_id所屬object的PoliceRank是否合乎access policy!
global current_user_id由於本場景需要多次操作PoliceSpy object,我們可以insert一個PoliceRank為DCP的Police object,並將其id指定給global current_user_id。
insert Police {name:= "test_DCP", police_rank:=PoliceRank.DCP};
set global current_user_id:= (
    select Police 
    filter .police_rank=PoliceRank.DCP limit 1).id;
update chen將永仁的經典台詞加入到classic_lines property中。
update chen 
set {
    classic_lines := .classic_lines ++ ["對唔住,我係差人。"],
};
update lau將建明的經典台詞加入到classic_lines property中。
update lau 
set {
    classic_lines := ["我以前無得揀,我而家想做好人。"],
};
insert ChenLauContact這是本劇中,兩人最後一次聯絡了。
insert ChenLauContact {
    how:= "面對面",
    detail:= "建明與永仁相約於天台上談判",
    `when`:=  (insert FuzzyTime {
                fuzzy_year:=2002,
                fuzzy_month:=11,
                fuzzy_day:=27,
                fuzzy_hour:=15,
                fuzzy_minute:=0,
                fuzzy_second:=0,
            }),
    where:= (select Location filter .name="天台"),
};
insert真.林國平真沒想到,國平竟然也是韓琛的臥底,第一次看到這段時,真是驚訝不已!
可是這麼一來,國平就不應該是Police而是GangsterSpy囉?我們應該刪掉國平 Police object,並新增一個國平 GangsterSpy object嗎?
這樣的話,之前國平 Police object的相關記錄都會被刪除(例如:CIBTeamTreat),這樣合理嗎?又或者我們應該重新去確認所有跟國平 Police object有關的object將其替換為國平 GangsterSpy object?
該怎麼做其實沒有標準的答案,不過一個比較常見的方法是使用soft delete。使用一個類似is_active的property來表達該object的存取狀態,而不真正將其從資料庫中刪除。畢竟在最後一幕之前,我們的確不知道國平是臥底,國平 Police object是一個合適的表達。
最後我們insert國平的GangsterSpy object如下:
with b:= assert_single((select Police filter .name="林國平"))
insert GangsterSpy {
      name:= b.name,
      nickname:= b.nickname,
      police_rank:= b.police_rank,
      gangster_boss:= hon,
      dept:= b.dept,
      actors:= b.actors
};
在緊湊的臥底對決中,其實導演與編劇也穿插了一些感情戲份,讓我們一起來看看吧。
我們insertMary,並指定其為建明的lover。
insert Character {
    name:= "Mary",
    eng_name:= "Mary",
    lover:= lau,
    actors:= (insert Actor{
        name:= "鄭秀文",
        eng_name:= "Sammi",
    }),
};
update lau 
set {
    lover:= assert_single((select Character filter .name="Mary")),
};
我們insert李心兒,並指定其為永仁的lover。
insert Character {
    name:= "李心兒",
    lover:= chen,
    actors:= (insert Actor{
        name:= "陳慧琳",
        eng_name:= "Kelly",
    }),
};
update chen 
set {
    lover:= assert_single((select Character filter .name="李心兒")),
};
現在我們面臨了一個有趣的情形,永仁看起來有兩個lover,但是我們的初始schema只設計了一個single link的lover。我們現在需要將這個single link的lover轉變為multi link的lovers。這其中其實包含了兩步的變更,第一步是將lover重新命名為lovers,第二步是將lovers由single link改為multi link。
您可以選擇做兩次migration,但其實EdgeDB相當聰明,大部份時間能夠猜中我們的意圖,讓我們試試用一步的migration來完成這個變化吧。我們變更Character如下:
type Character extending Person {
    classic_lines: array<str>;
    multi lovers: Character;
    multi actors: Actor;
}
接著於命令列執行edgedb migration create。
did you drop link 'lover' of object type 'default::Character'? [y,n,l,c,b,s,q,?]
> n
did you rename link 'lover' of object type 'default::Character' to 'lovers'? [y,n,l,c,b,s,q,?]
> y
did you convert link 'lovers' of object type 'default::Character' to 'multi' cardinality? [y,n,l,c,b,s,q,?]
> y
留意第一個選項我們選擇了n,於是EdgeDB試著詢問我們。如果不是要drop的話,是否是要rename。如果是要rename的話,是否由single link改為multi link。如此一來,我們原來於lover中所指向的object,在於命令列執行edgedb migrate後也會一併帶到lovers。
如果第一個選項我們選擇了y,EdgeDB會認為我們想先drop掉lover,然候加上一個multi link的lovers。如此一來lovers將會是空set,我們需要在於命令列執行完edgedb migrate後,手動將原來lover所指向的object加進來。
由這個例子可以知道,migration時不一定只能選擇y,應該視當下需求來決定。
由於此處進行了migration,所以需要再一次設定global current_user_id。
set global current_user_id:= (
    select Police 
    filter .police_rank=PoliceRank.DCP limit 1).id;
最後我們insertMay,並將May加入到chen的lovers。
# end migration needs to be applied before running this query
insert Character{
    name:= "May",
    eng_name:= "May",
    lovers:= chen,
    actors:= (insert Actor{
        name:= "蕭亞軒",
        eng_name:= "Elva",
    }),
};
update chen 
set {
    lovers+= assert_single((select Character filter .name="May")),
};
我們可以確認chen的lovers內確實有心兒及May。
select chen.lovers.name;
{'李心兒', 'May'}
detached假設永仁覺得自己有太多lovers,想利用update幫他斷捨離,但卻發現有時候lovers會被設為空set,他百思不得其解,讓我們一起來看看永仁遇到的情況。永仁一共嘗試了下列五種query,只有query1會將lovers設為空set,query2~query5都可以成功將lovers設定為心兒一人:
#❌
update Character filter .name="陳永仁"
set {lovers:= (select Character filter .name="李心兒")};
#✅
update chen
set {lovers:= (select Character filter .name="李心兒")};
#✅
with ch:= (select Character filter .name="陳永仁")
update ch
set {lovers:= (select Character filter .name="李心兒")};
#✅
update PoliceSpy filter .name="陳永仁"
set {lovers:= (select Character filter .name="李心兒")};
#✅
update Character filter .name="陳永仁"
set {lovers:= (select detached Character filter .name="李心兒")};
原來問題出在query1中,我們在update Character的set(關鍵字)內再次使用了select Character。這個Character將會是外面update Character filter .name="陳永仁"語法中的EdgeDBset,而不是Character這個object type。當想要在各種top-level EdgeQL statements(select, insert, update及delete)內再次引用同一個object type時,需要使用detached。
這是個很常見的錯誤,以上提供了四種修訂方法:
alias,如chen 。with區塊內,暫時命名一個變數,如ch。update時改使用其它object type,如PoliceSpy。detached。insert此場景的Sceneinsert Scene {
      title:= "我想做個好人", 
      detail:= "建明與永仁於天台相見,不料國平也趕到。永仁事先已報警,想持槍壓著" ++
               "建明到樓下交予警方。不料,於進電梯時被國平擊斃,原來他也是韓琛安" ++ 
               "裝於警隊的臥底。國平向建明表明身份,希望之後一起合作。但最終建明" ++
               "選擇於電梯中殺死國平,並營造永仁與國平雙雙死於槍戰的假象。事後," ++
               "心兒於葉校長遺物中發現永仁臥底檔案,恢復其警察身份,並由建明", ++
               "代表行禮。"
      who:=  (select Police filter .name="林國平") union 
             (select PoliceSpy filter .name="林國平") union
             {chen, lau},
      `when`:= assert_single(
                  (
                      select FuzzyTime 
                      filter .fuzzy_fmt="2002/11/27_15:00:00_ID"
                  )
              ),
      where:= (select Location filter .name="天台"),    
};
uuid選取object的技巧假設我們想選擇一開始建立的PoliceRank為DCP的Police object,該怎麼寫query呢?
最簡單的方法應該是filter .name="test_DCP"了吧,像是:
select Police filter .name="test_DCP";
但是假設我們只有該object str型態的id的話,又該怎麼選取呢?您可能會寫出以下query:
with pid:= <str>(select Police filter .name="test_DCP").id,
select Police filter .id=<uuid>pid;
但是除了這種經典的寫法外,EdgeDB還提供了以下寫法:
with pid:= <str>(select Police filter .name="test_DCP").id,
select <Police><uuid>pid;
pid此時為str型態的uuid,我們可以在前面使用<Police><uuid>來casting而取得object。
當想要使用上述寫法並搭配shape construction時,需加上(),例如:
# ✅
with pid:= <str>(select Police filter .name="test_DCP").id,
select (<Police><uuid>pid) {*};
而下面這兩種寫法是不被允許的:
# ❌
with pid:= <str>(select Police filter .name="test_DCP").id,
select <Police><uuid>pid {*};
# ❌
with pid:= <str>(select Police filter .name="test_DCP").id,
select {*} <Police><uuid>pid;
另外,如果您已經有一個EdgeDBset,也可以進行類似的操作,例如:
with pid:= (select Police filter .name="test_DCP").id,
select <Police>pid;
讓我們用上述技巧來刪除一開始建立的PoliceRank為DCP的Police object,並reset global current_user_id。
with pid:= <str>(select Police filter .name="test_DCP").id,
delete <Police><uuid>pid;
reset global current_user_id;
根據訪談,於拍攝時間只有華仔與編劇導演等少部份人知道,國平也是韓琛所派臥底,甚至連飾演國平的林家棟都是到最後一幕快開拍前才知道。當時他擔心前面的戲份是不是有演得不合劇情的地方,華仔說沒問題,他要的就是這種反差感。